vue2之keep

您所在的位置:网站首页 force reload是什么意思 vue2之keep

vue2之keep

2023-10-20 01:33| 来源: 网络整理| 查看: 265

vue2之keep-alive详解 起因

我有个朋友,在维护公司的项目时候,出现了一个bug,就是使用keepalive的页面居然有时候没有缓存效果。真是奇了个大怪,下面是问题代码。于是本着助人为乐的好品质,我就帮他看了一下,发现事情不简单 image.png

keep-alive是什么

首先在开始前,我们要先了解一下keep-alive这个组件的基本用法。 我这边就长话短说。这个组件的功能只有一个那就是缓存组件,可以让其包裹的组件不销毁,起到一个缓存的作用。

接受可选的三个参数

max 缓存的最大组件数量,支持数字和字符串 include 需要缓存的数据,支持字符串,数组,正则 exclude 不需要缓存的数据,支持字符串,数组,正则 具体的详细介绍可以看这里 keep-alive官方介绍 寻找bug

了解了基本用法,那就要去定位问题了

会不会是include的值匹配的范围太小,导致无法缓存?

会不会是include的值让其无法缓存呢?于是我让我的那个朋友打印了一下include中的值,好家伙返回的是组件name属性组成的数组,那应该不是这个问题了,因为需要缓存的组件在这个数组里

会不会exclude的值匹配的范围太大,导致无法缓存?

不是include造成的那应该就是exclude这个数据太大了,导致其无法缓存。于是我又让我那个朋友打印了一下exclude。结果出人意料,居然是个空数组。我的天啊。这是啥原因?

我看了一下组件可以接受max这个参数,是不是组件内部默认设置了最大缓存数,导致缓存失败?

keep-alive内部是否设置了默认max?

对于这个疑问,本着解决问题的态度,于是我把vue源码拉取下来,准备一探究竟

源码路径src =>core => components => keep-alive.ts github:github.com/vuejs/vue/b… 我这边看的是2.7版本的,好像之前的flow语法全给用ts改写了,不得不说vue团队还是挺强大的

怀着好奇心情看源码 max是不是设置了默认值

于是我打开了源码keep-alive.ts,在当前文件搜了一下max。有两处引用,一次是props相关,一次是使用相关

这是定义props的地方,可以看出来,这里没有做默认值处理,只是定义了接受传入的props

image.png

继续看使用的地方,好像也没有默认值处理啊,只是判断max是否定义,和超出他才调用pruneCacheEntry这个方法

image.png

pruneCacheEntry做了什么

那么pruneCacheEntry是什么,他做了什么呢,从变量名,可以大概猜出,这个去除缓存的函数

我这边把函数改成js,方便大家阅读

image.png

看得出,他接受了四个参数,分别是cache,key,keys,current

current看起来是当前keep-alive组件的虚拟dom,那cache,keys是什么呢,从哪里定义的呢

cache,keys是什么呢

于是我找到了这两个变量的声明地方。原来在created这个生命周期中声明了

cache 是一个空对象 keys 是一个空数组 从字面意思可以看出,这两个是缓存用的,具体他是咋用的?

image.png

初始化

原来在组件初始化的时候,通过mounted这个生命周期调用了cacheVNode这个方法,这个方法拥有缓存的奇效

image.png 具体实现如下

image.png 好家伙这里实现和之前调用max的地方居然是同一个函数

取出组件中的vnodeToCache和keyToCache, 从vnodeToCache中获取的组件名tag,组件实例和选项 将组件存放在缓存中 并把匹配的key值推入keys尾部 清空当前的vnodeToCache 原来如此,那么vnodeToCache和keyToCache从哪里定义的呢 vnodeToCache和keyToCache

我在render中找到了定义,为了解读方便,我把ts改成了js,并添加了点注释

render() { const slot = this.$slots.default; const vNode = getFirstChildren(slot); const componentsOptions = vNode.componentOptions; // 获取第一个有效组件的配置项 if (componentsOptions) { const { exclude, include } = this; const name = getComponentName(componentsOptions); //如果传递了限制 这里如果没有命中规则,直接返回 if ((include && (!name || !matches(include, name))) || (exclude && name && matches(exclude, name))) { console.log('不符合缓存条件,直接返回'); return vNode; } console.log(componentsOptions); const { cache, keys } = this; // 这里获取组件的key,如果用户没有传入key,则使用组件的cid+组件的名称作为key const key = vNode.key == null ? componentsOptions.Ctor.cid + (componentsOptions.tag ? `::${componentsOptions.tag}` : '') : vNode.key; if (cache[key]) { console.log('缓存命中'); // 将命中的缓存组件拿出来覆盖当前vNode的componentInstance console.log(cache[key]); vNode.componentInstance = cache[key].componentInstance; // 由于需要将当前组件变成最新被使用 // 1.先删除命中的key remove(keys, key); // 2.重新追加到尾部,这样可以保证这个组件是最近被使用 keys.push(key); } else { console.log('未命中缓存,将其加入缓存', key); this.vNodeToCache = vNode; this.keyToCache = key; } vNode.data.keepAlive = true; } return vNode; }

原来是这样,大致流程如下

首先通过slot获取到了当前默认插槽里的组件 通过getFirstChildren获取到了正在渲染的第一个组件,这也解释了为什么我在keepalive里写了好多组件,页面渲染的永远是第一个组件 拿到了第一个正在渲染的组件,取出他的组件名name,先判断用户是否传递了include和exclude,如果匹配上了,且不需要被缓存,就直接返回这个组件 如果可以被缓存,那么通过判断他的key是否存在,来生成一个唯一key 通过这个key来从缓存cache查找是否被缓存过 如果没有缓存过,把key和当前组件分别赋值给vNodeToCache,keyToCache 如果缓存过,那么将缓存中的这个key对应的组件信息赋值给当前渲染的组件,并且返回 问题解决

等等,我好像找到答案了,通过key去缓存的组件,在看问题代码 image.png

我那个朋友在router-view中传递了key,于是我让他打印了一下,发现每次路由切换,都会生成一个不同的viewkey。我好像找到答案了,我让他去除了key,发现可以缓存了。ok了,问题解决了!

如何更新数据

问题是解决了,可是cacheVNode只在mounted的时候调用,那他如何缓存多个呢?好奇的我发现,在updated中也调用了。

image.png 好了,我悟了。原来如此,在每次render后,都会调用这个函数,从而实现缓存的功效

include和exclude的观察

问题又来了,如果include和exclude变化了,如何进行缓存呢?

于是我在mounted中看到了定义,对这两个参数进行watch监听,每次变换,重新执行pruneCache这个函数

image.png

pruneCache做了什么

我又把ts改成js并且加了点注释,这个函数的功能就只有一个,通过传入的参数,去匹配组件名字是否满足缓存条件,如果不满足,那就去除这个缓存

const pruneCache = (keepAliveInstance, filter) => { // 从keepalive组件中获取缓存的组件数据和keys列表 // 循环获取缓存的key 进行卸载 const { cache, keys, _vnode } = keepAliveInstance; for (let key in cache) { if (cache[key]) { const name = cache[key].name; // 使用name去匹配,不满足这个条件的就卸载 if (name && !filter(name)) { console.log('卸载===>', name); pruneCacheEntry(cache, key, keys, _vnode); } } } }; 如何进行最大缓存约束

在这里,我又有个问题,如果设置了最大缓存数,内部如何判断哪些是需要缓存的,哪些是需要删除的?

其实这个问题在函数pruneCacheEntry中有了答案,我这边简单加了点注释。该函数会清除缓存

const pruneCacheEntry = (cache, key, keys, current) => { const entry = cache[key]; if (entry && (!current || current.tag !== entry.tag)) { entry.componentInstance.$destroy(); } // cache中的匹配的数据变为null cache[key] = null; // 删除数组中的数据 remove(keys, key); };

这边还得依靠文章开头摘取的代码,默认认为第一项为过期,将它从缓存中去除

if (max && keys.length > parseInt(max)) { // 卸载过期的缓存,这边默认是数组的第一项 pruneCacheEntry(cache, keys[0], keys, this._vnode); } 如何保证经常使用的组件不会从缓存中去除

看到这里我总感觉怪怪的,如果我的组件第一个缓存,岂不是第一个从缓存中去除

答案不是的

if (cache[key]) { // 缓存命中 // 将命中的缓存组件拿出来覆盖当前vNode的componentInstance console.log(cache[key]); vNode.componentInstance = cache[key].componentInstance; // 由于需要将当前组件变成最新被使用 // 1.先删除命中的key remove(keys, key); // 2.重新追加到尾部,这样可以保证这个组件是最近被使用 keys.push(key); }

这样可以保证最近使用的一次组件在末尾了。好家伙那些写框架的人都这么牛逼的吗

结语

通过本次问题,解决了朋友的问题,也让我对vue中keep-alive组件的实现有了更多的了解。感谢大家看到这里。如果觉得不错可以给小弟点个赞😀😀。如果有理解错误的地方,可以提出,我改!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3